/**
 * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright ownership. Apereo
 * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at the
 * following location:
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apereo.portal.version;

import com.google.common.collect.ImmutableMap;
import java.util.Map;
import javax.annotation.Resource;
import org.apereo.portal.shell.PortalShellBuildHelper;
import org.apereo.portal.version.dao.VersionDao;
import org.apereo.portal.version.om.Version;
import org.apereo.portal.version.om.Version.Field;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContextException;
import org.springframework.stereotype.Component;

/**
 * Bean that verifies the product version numbers in the configuration versus the database on
 * startup
 */
@Component
public class VersionVerifier implements InitializingBean {
    protected final Logger logger = LoggerFactory.getLogger(getClass());

    private Map<String, Version> requiredProductVersions;
    private VersionDao versionDao;
    private Version.Field updatePolicy = Version.Field.LOCAL;
    private PortalShellBuildHelper portalShellBuildHelper;

    @Resource(name = "productVersions")
    public void setRequiredProductVersions(Map<String, Version> requiredProductVersions) {
        this.requiredProductVersions = ImmutableMap.copyOf(requiredProductVersions);
    }

    @Autowired
    public void setVersionDao(VersionDao versionDao) {
        this.versionDao = versionDao;
    }

    @Autowired
    public void setPortalShellBuildHelper(PortalShellBuildHelper portalShellBuildHelper) {
        this.portalShellBuildHelper = portalShellBuildHelper;
    }

    @Value("${org.apereo.portal.version.autoUpdatePolicy:LOCAL}")
    public void setUpdatePolicy(Version.Field updatePolicy) {
        if (updatePolicy != null
                && updatePolicy != Version.Field.LOCAL
                && updatePolicy != Version.Field.PATCH) {
            throw new IllegalArgumentException("Only null, LOCAL, and PATCH updates are allowed");
        }
        this.updatePolicy = updatePolicy;
    }

    @SuppressWarnings("FallThrough")
    @Override
    public void afterPropertiesSet() throws Exception {
        for (final Map.Entry<String, Version> productVersionEntry :
                this.requiredProductVersions.entrySet()) {
            final String product = productVersionEntry.getKey();
            final Version dbVersion = this.versionDao.getVersion(product);
            if (dbVersion == null) {
                throw new ApplicationContextException(
                        "No Version exists for "
                                + product
                                + " in the database. Please check the upgrade instructions for this release.");
            }

            final Version codeVersion = productVersionEntry.getValue();
            final Field mostSpecificMatchingField =
                    VersionUtils.getMostSpecificMatchingField(dbVersion, codeVersion);

            switch (mostSpecificMatchingField) {
                    // Versions completely match
                case LOCAL:
                    {
                        logger.info(
                                "Software and Database versions are both {} for {}",
                                dbVersion,
                                product);
                        continue;
                    }
                    // Versions match except for local part
                case PATCH:
                    // Versions match except for patch.local part
                case MINOR:
                    {
                        // If db is before code and auto-update is enabled run hibernate-update
                        final Field upgradeField = mostSpecificMatchingField.getLessImportant();

                        if (dbVersion.isBefore(codeVersion)
                                && this.updatePolicy != null
                                && (upgradeField.equals(this.updatePolicy)
                                        || upgradeField.isLessImportantThan(this.updatePolicy))) {
                            logger.info(
                                    "Automatically updating database from {} to {} for {}",
                                    dbVersion,
                                    codeVersion,
                                    product);

                            this.portalShellBuildHelper.hibernateUpdate(
                                    "automated-hibernate-update", product, true, null);
                            continue;
                        } else if (codeVersion.isBefore(dbVersion)) {
                            // It is ok to run older code on a newer DB within the local/patch range
                            continue;
                        }
                    }
                    // Versions match except for minor.patch.local part
                case MAJOR:
                    // Versions do not match at all
                default:
                    {
                        if (dbVersion.isBefore(codeVersion)) {
                            throw new ApplicationContextException(
                                    "Database Version for "
                                            + product
                                            + " is "
                                            + dbVersion
                                            + " but the code version is "
                                            + codeVersion
                                            + ". Please check the upgrade instructions for this release");
                        } else {
                            throw new ApplicationContextException(
                                    "Database Version for "
                                            + product
                                            + " is "
                                            + dbVersion
                                            + " but the code version is "
                                            + codeVersion
                                            + ". It is not possible to run ");
                        }
                    }
            }
        }
    }
}
